﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Common.Infrastructure
{
    public static class ExpressionExtensions
    {
        /// <summary>
        /// 以特定的条件运行组合两个Expression表达式.
        /// </summary>
        /// <typeparam name="T">表达式的主实体类型.</typeparam>
        /// <param name="first">第一个Expression表达式.</param>
        /// <param name="second">要组合的Expression表达式.</param>
        /// <param name="merge">组合条件运算方式.</param>
        /// <returns>组合后的表达式.</returns>
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            Dictionary<ParameterExpression, ParameterExpression> map = first.Parameters.Select((ParameterExpression f, int i) => new
            {
                f,
                s = second.Parameters[i],
            }).ToDictionary(p => p.s, p => p.f);
            Expression arg = ExpressionExtensions.ParameterRebinder.ReplaceParameters(map, second.Body);
            return Expression.Lambda<T>(merge(first.Body, arg), first.Parameters);
        }

        /// <summary>
        /// 以 Expression.AndAlso 组合两个Expression表达式.
        /// </summary>
        /// <typeparam name="T">表达式的主实体类型.</typeparam>
        /// <param name="first">第一个Expression表达式.</param>
        /// <param name="second">要组合的Expression表达式.</param>
        /// <returns>组合后的表达式.</returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, new Func<Expression, Expression, Expression>(Expression.AndAlso));
        }

        /// <summary>
        /// 以 Expression.OrElse 组合两个Expression表达式.
        /// </summary>
        /// <typeparam name="T">表达式的主实体类型.</typeparam>
        /// <param name="first">第一个Expression表达式.</param>
        /// <param name="second">要组合的Expression表达式.</param>
        /// <returns>组合后的表达式.</returns>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, new Func<Expression, Expression, Expression>(Expression.OrElse));
        }

        private class ParameterRebinder : ExpressionVisitor
        {
            private readonly Dictionary<ParameterExpression, ParameterExpression> map;

            private ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ExpressionExtensions.ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (this.map.TryGetValue(node, out ParameterExpression parameterExpression))
                {
                    node = parameterExpression;
                }

                return base.VisitParameter(node);
            }
        }
    }
}
